package com.pan.framework.tomcat;

import com.pan.framework.anno.controller.MyController;
import com.pan.framework.anno.controller.MyGetMapping;
import com.pan.framework.anno.controller.MyPostMapping;
import com.pan.framework.tomcat.entity.ServletInfo;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class MyServletFactory {


    Method getMethod;
    Method postMethod;
    Method putMethod;
    Method deleteMethod;

    ClassLoader myLoader;

    public MyServletFactory(ClassLoader classLoader) {
        myLoader = classLoader;
        try {
            getMethod = HttpServlet.class.getDeclaredMethod("doGet", HttpServletRequest.class, HttpServletResponse.class);
            getMethod.setAccessible(true);
            postMethod = HttpServlet.class.getDeclaredMethod("doPost", HttpServletRequest.class, HttpServletResponse.class);
            postMethod.setAccessible(true);
            putMethod = HttpServlet.class.getDeclaredMethod("doPut", HttpServletRequest.class, HttpServletResponse.class);
            putMethod.setAccessible(true);
            deleteMethod = HttpServlet.class.getDeclaredMethod("doDelete", HttpServletRequest.class, HttpServletResponse.class);
            deleteMethod.setAccessible(true);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

    public List<ServletInfo> getServletInfoList(List<Object> controllers){
        List<ServletInfo> result = new ArrayList<>();
        controllers.stream().map(this::getServletInfoList)
                .forEach(li->result.addAll(li));
        return result;
    }

    public List<ServletInfo> getServletInfoList(Object controller) {
        Class<?> controllerClass = controller.getClass();
        if (!controllerClass.isAnnotationPresent(MyController.class)) {
            throw new RuntimeException(controllerClass.getName()+"这没有Controller注解");
        }
        MyController myController = controllerClass.getAnnotation(MyController.class);
        String controllerPath = myController.path();
        String controllerName = myController.name();
        Method[] methods = controller.getClass().getMethods();

        List<ServletInfo> resultList = Arrays.stream(methods)
                .map(method -> createServlet(method, controller, controllerPath, controllerName))
                .filter(si -> si != null)
                .collect(Collectors.toList());
        if (resultList.size() == 0){
            return null;
        }
        return resultList;
    }


    private ServletInfo createServlet(Method method, Object controller, String controllerPath, String controllerName) {
        ServletInfo servletInfo = new ServletInfo();
        servletInfo.setControlPath(controllerPath);
        String servletName = controllerName +"_"+ method.getName();
        servletInfo.setServletName(servletName);

        Method annoTypeMethod;
        if (method.isAnnotationPresent(MyGetMapping.class)) {
            annoTypeMethod = getMethod;
            String methodPath = method.getAnnotation(MyGetMapping.class).path();
            servletInfo.setMethodPath(methodPath);
        } else if (method.isAnnotationPresent(MyPostMapping.class)) {
            annoTypeMethod = postMethod;
            String methodPath = method.getAnnotation(MyPostMapping.class).path();
            servletInfo.setMethodPath(methodPath);
        } else {
            return null;
        }
        Class<?>[] parameterTypes = method.getParameterTypes();
        if (parameterTypes.length > 2) {
            throw new RuntimeException(servletName + " 遇到的参数超过2个");
        }
        for (Class<?> parameterType : parameterTypes) {
            if (parameterType != HttpServletResponse.class && parameterType != HttpServletRequest.class) {
                throw new RuntimeException(servletName + " 遇到的参数不是 response 和 request");
            }
        }

        HttpServlet httpServlet = annoTypeMethod.equals(getMethod)
                ? new HttpServlet() {
            @Override
            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//                super.doGet(req, resp);
                servletInvoke(req, resp, parameterTypes, method, controller);
            }
        } : new HttpServlet() {
            @Override
            protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//                super.doPost(req, resp);
                servletInvoke(req, resp, parameterTypes, method, controller);
            }
        };
        //这里代理一下方便以后扩展
        //todo： 暂时放一下 servlet涉及打破双亲委托，不能直接代理？？ 有时间在研究一下
//        HttpServlet proxy = (HttpServlet) Proxy.newProxyInstance(
//                ClassLoader.getSystemClassLoader().getParent().getParent(),
//                httpServlet.getClass().getInterfaces(),
//                new InvocationHandler() {
//                    @Override
//                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//
//                        if (method.equals(annoTypeMethod)) {
//                            method.invoke(httpServlet, args);
//                        }
//                        else {
//                            method.invoke(httpServlet,args);
//                        }
//                        return null;
//                    }
//                }
//        );
        servletInfo.setServlet(httpServlet);
        return servletInfo;
    }

    public boolean isImplement(Object target,Class interfaceClass){
        for (Class<?> anInterface : target.getClass().getInterfaces()) {
            if (anInterface == interfaceClass){
                return true;
            }
        }
        return false;
    }
    private void servletInvoke(HttpServletRequest req,
                               HttpServletResponse resp,
                               Class<?>[] parameterTypes,
                               Method method, Object controller) {
        Object[] args = new Object[parameterTypes.length];
        for (int i = 0; i < parameterTypes.length; i++) {
            if (isImplement(req,parameterTypes[i])) {
                args[i] = req;
            } else if (isImplement(resp,parameterTypes[i]) ) {
                args[i] = resp;
            }
        }
        resp.setContentType("text/json");
        resp.setCharacterEncoding("utf-8");
        try {
            String json = (String) method.invoke(controller, args);
            resp.getWriter().print(json);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
